/*  Copyright (c) 2012 William Rogers and James Boud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using SlimDX;
using SlimDX.Direct3D11;
using Microsoft.Xna.Framework.Content;
using JBBRXG11ContentFBXModel;

namespace JBBRXG11
{
    public class Model : ContentTypeReader<Model>, IDisposable
    {
        #region Shader source
        internal static string AnimatedModelEffectSource =
"texture2D Texture;" +
"" +
"cbuffer perframe" +
"{" +
"    float4x4 World;" +
"};" +
"    " +
"cbuffer fixed" +
"{" +
"    float4x4 View;" +
"    float3 CameraPosition;" +
"    float4x4 Projection;" +
"};" +
"" +
"float3 LightDirection = normalize(float3(1, 3, 2));" +
"float4 ModelColour = float4(1.0, 1.0, 1.0, 1.0);" +
"" +
"SamplerState samLinear" +
"{" +
"    Filter = MIN_MAG_MIP_LINEAR;" +
"    AddressU = Wrap;" +
"    AddressV = Wrap;" +
"};" +
"" +
"struct VS_IN" +
"{" +
"    float4 Pos: POSITION;" +
"    float4 BlendI : BLENDINDICES;" +
"    float4 BlendW : BLENDWEIGHT;" +
"    float3 Norm: NORMAL;" +
"    float2 TexC: TEXTURECOORD;" +
"};" +
"" +
"struct PS_IN" +
"{" +
"	float4 Pos: SV_POSITION;" +
"	float2 TexC: TEXTURECOORD;" +
"	float4 Col: COLOR;" +
"};" +
"" +
"PS_IN VS( VS_IN input )" +
"{" +
"    PS_IN output;" +
"    output.Pos = mul(input.Pos, mul(mul(World, View),Projection));" +
"    output.TexC = input.TexC;" +
"    output.Col = ModelColour * (0.1 + 0.9 * saturate(dot(mul(input.Norm, (float3x3)World), LightDirection)));" +
"    return output;" +
"}" +
"" +
"float4 PS( PS_IN input ) : SV_Target" +
"{" +
"    return input.Col * Texture.Sample(samLinear, input.TexC);" +
"}" +
"" +
"technique10 Render" +
"{" +
"    pass P0" +
"    {" +
"        SetVertexShader( CompileShader( vs_4_0, VS() ) );" +
"        SetHullShader( NULL );\r\n" +
"        SetDomainShader( NULL );\r\n" +
"        SetGeometryShader( NULL );\r\n" +
"        SetPixelShader( CompileShader( ps_4_0, PS() ) );" +
"    }" +
"}";
        #endregion

        GraphicsDevice device;
        ModelMeshCollection meshes;
        SkinningData animationdata;
        bool hastexture, hasnormals, hasskinning, animated;

        // Information about the currently playing animation clip.
        Microsoft.Xna.Framework.Matrix[] boneTransforms, worldTransforms, skinTransforms;
        AnimationClip currentclip;   // null if no animation is playing
        TimeSpan currentanimationtime;
        int currentkeyframe;
        Microsoft.Xna.Framework.Matrix animationroot;
        //Vector3 offset;
        internal bool CurrentlyAnimating { get { return animated && currentclip != null; } }
        internal Microsoft.Xna.Framework.Matrix[] SkinTransforms { get { return skinTransforms; } }

        public ModelMeshCollection Meshes { get { return meshes; } }

        public Model() { device = null; meshes = null; }

        protected override Model Read(ContentReader input, Model existingInstance)
        {
            ModelInfoStuff mis = new ModelInfoStuff();
            mis.stuff = input.ReadString();
            if (mis.stuff.Substring(0, ModelInfoStuff.versionstring.Length) != ModelInfoStuff.versionstring)
            {
                throw new Exception("Obsolete Model file format\r\nPlease recompile using \"Rebuild Solution\"");
            }
            // ModelInfoStuff
            int length;
            length = input.ReadInt32();
            mis.meshes = new my_mesh[length];
            for (int m = 0; m < mis.meshes.Length; m++)
            {
                
                // my_mesh
                my_mesh mesh = new my_mesh();
                mesh.name = input.ReadString();
                Microsoft.Xna.Framework.Vector3 centre = input.ReadVector3();
                float radius = input.ReadSingle();
                mesh.sphere = new Microsoft.Xna.Framework.BoundingSphere(centre, radius);

                length = input.ReadInt32();
                mesh.parts = new my_mesh_part[length];
                for (int p = 0; p < mesh.parts.Length; p++)
                {
                    // my_mesh_part
                    my_mesh_part part = new my_mesh_part();
                    part.vertexOffset = input.ReadInt32();
                    part.numvertices = input.ReadInt32();
                    part.primitivecount = input.ReadInt32();
                    part.startindex = input.ReadInt32();

                    length = input.ReadInt32();
                    part.vertexelements = new my_vertex_element[length];
                    for (int e = 0; e < part.vertexelements.Length; e++)
                    {
                        //TRY: Reading (int) of Format, Method, Usage.
                        int Offset = input.ReadInt32();
                        int UsageIndex = input.ReadInt32();
                        int VertexElementFormat = input.ReadInt32();
                        int VertexElementUsage = input.ReadInt32();
                        part.vertexelements[e] = new my_vertex_element();
                        part.vertexelements[e].ve
                            = new Microsoft.Xna.Framework.Graphics.VertexElement
                                (Offset,
                                 (Microsoft.Xna.Framework.Graphics.VertexElementFormat)VertexElementFormat,
                                 (Microsoft.Xna.Framework.Graphics.VertexElementUsage)VertexElementUsage,
                                 UsageIndex);
                    }

                    part.noindices = input.ReadBoolean();
                    if (part.noindices)
                    {
                        part.computeadjacencyindices = false;
                        part.use32bitindices = false;
                        part.p16Indices = null;
                        part.p32Indices = null;
                    }
                    else   // There are indices to read
                    {
                        part.computeadjacencyindices = input.ReadBoolean();
                        part.use32bitindices = input.ReadBoolean();
                        if (!part.use32bitindices)  // 16 bit indices
                        {
                            length = input.ReadInt32();
                            part.p16Indices = new ushort[length];
                            for (int i = 0; i < part.p16Indices.Length; i++)
                            {
                                part.p16Indices[i] = input.ReadUInt16();
                            }
                            part.p32Indices = null;
                        }
                        else                        // 32 bit indices
                        {
                            length = input.ReadInt32();
                            part.p16Indices = null;
                            part.p32Indices = new int[length];
                            for (int i = 0; i < part.p32Indices.Length; i++)
                            {
                                part.p32Indices[i] = input.ReadInt32();
                            }
                        }
                    }

                    length = input.ReadInt32();
                    part.pVertices = input.ReadBytes(length);
                    length = input.ReadInt32();
                    part.texturekeys = new string[length];
                    for (int k = 0; k < part.texturekeys.Length; k++)
                        part.texturekeys[k] = input.ReadString();
                    length = input.ReadInt32();
                    part.texturefilenames = new string[length];
                    for (int n = 0; n < part.texturefilenames.Length; n++)
                        part.texturefilenames[n] = input.ReadString();
                    mesh.parts[p] = part;
                }
                mis.meshes[m] = mesh;
                
            }

            bool haveskinning = input.ReadBoolean();
            if (!haveskinning)
            {
                mis.skinning = null;
            }
            else
            {
                mis.skinning = new SkinningData();
                int nclips = input.ReadInt32();
                mis.skinning.AnimationClips = new System.Collections.Generic.Dictionary<string, AnimationClip>();
                for (int c = 0; c < nclips; c++)
                {
                    // Animation clip
                    string key = input.ReadString();
                    AnimationClip clip = new AnimationClip();
                    mis.skinning.AnimationClips[key] = clip;
                    clip.Duration = new TimeSpan(input.ReadInt64());
                    length = input.ReadInt32();
                    clip.Keyframes = new Keyframe[length];
                    for (int k = 0; k < clip.Keyframes.Length; k++)
                    {
                        // Keyframe
                        clip.Keyframes[k].Bone = input.ReadInt32();
                        clip.Keyframes[k].Time = new TimeSpan(input.ReadInt64());
                        clip.Keyframes[k].Transform = input.ReadMatrix();
                    }
                }
                length = input.ReadInt32();
                mis.skinning.BindPose = new Microsoft.Xna.Framework.Matrix[length];
                for (int b = 0; b < mis.skinning.BindPose.Length; b++)
                    mis.skinning.BindPose[b] = input.ReadMatrix();
                length = input.ReadInt32();
                mis.skinning.InverseBindPose = new Microsoft.Xna.Framework.Matrix[length];
                for (int b = 0; b < mis.skinning.InverseBindPose.Length; b++)
                    mis.skinning.InverseBindPose[b] = input.ReadMatrix();
                length = input.ReadInt32();
                mis.skinning.SkeletonHierarchy = new int[length];
                for (int s = 0; s < mis.skinning.SkeletonHierarchy.Length; s++)
                    mis.skinning.SkeletonHierarchy[s] = input.ReadInt32();
            }

            // Can't use this instance as it belongs to the content manager
            // which may reuse it, overwriting this object
            Model newmodel = new Model();
            Game mygame = GraphicsDeviceManager.GetGame(Game.GraphicsDevice);
            newmodel.ProcessInfo(Game.GraphicsDevice, mygame.Content, mis);
            mygame.UserContentAdd(newmodel);
            return newmodel;
        }

        void ProcessInfo(GraphicsDevice dev, ContentManager content, ModelInfoStuff mis)
        {
            hastexture = false;  // Test here is a bit crude - should be done per part
            hasnormals = false;
            hasskinning = false;
            device = dev;
            List<ModelMesh> meshlist = new List<ModelMesh>();
            foreach (my_mesh mesh in mis.meshes)
            {
                int vertexpositionbyteoffset = 0;
                List<ModelMeshPart> partlist = new List<ModelMeshPart>();
                List<Effect> effectlist = new List<Effect>();

                foreach (my_mesh_part part in mesh.parts)
                {
                    // Vertex declaration
                    Microsoft.Xna.Framework.Graphics.VertexElement[] velements;
                    velements = new Microsoft.Xna.Framework.Graphics.VertexElement[part.vertexelements.Length];
                    for (int v = 0; v < velements.Length; v++)
                    {
                        velements[v] = part.vertexelements[v].ve;
                        if (velements[v].VertexElementUsage
                                  == Microsoft.Xna.Framework.Graphics.VertexElementUsage.Position)
                            vertexpositionbyteoffset = velements[v].Offset;
                        if (velements[v].VertexElementUsage
                                  == Microsoft.Xna.Framework.Graphics.VertexElementUsage.TextureCoordinate)
                            hastexture = true;
                        if (velements[v].VertexElementUsage
                                  == Microsoft.Xna.Framework.Graphics.VertexElementUsage.Normal)
                            hasnormals = true;
                        if (velements[v].VertexElementUsage
                                  == Microsoft.Xna.Framework.Graphics.VertexElementUsage.BlendIndices)
                            hasskinning = true;
                    }
                    VertexDeclaration vdecl = new VertexDeclaration(velements);

                    // Vertex buffer
                    VertexBuffer vbuf = new VertexBuffer(device, vdecl, part.pVertices.Length / vdecl.GetVertexStrideSize(), BufferUsage.WriteOnly);
                    vbuf.SetData<byte>(part.pVertices);

                    // Index buffer
                    IndexBuffer ibuf;
                    if (part.noindices)
                    {
                        ibuf = null;
                    }
                    else
                    {
                        if (!part.use32bitindices)  // 16 bit indices
                        {
                            ibuf = new IndexBuffer(device, part.p16Indices.Length * 2,
                                                   BufferUsage.WriteOnly,
                                                   Microsoft.Xna.Framework.Graphics.IndexElementSize.SixteenBits);
                            ibuf.SetData<ushort>(part.p16Indices);
                        }
                        else                        // 32 bit indices
                        {
                            ibuf = new IndexBuffer(device, part.p32Indices.Length * 4,
                                                   BufferUsage.WriteOnly,
                                                   Microsoft.Xna.Framework.Graphics.IndexElementSize.ThirtyTwoBits);
                            ibuf.SetData<int>(part.p32Indices);
                        }
                    }

                    // Adjacency index buffer
                    IndexBuffer adjibuf = null;
                    if (part.computeadjacencyindices)
                    {
                        if (!part.use32bitindices)  // 16 bit indices
                        {
                            ushort[] adjacencies;
                            string report;
                            JBBRXG11ModelAdjacency.AppendAdjacency(part.pVertices,
                                                                   part.vertexOffset,
                                                                   vertexpositionbyteoffset,
                                                                   vdecl.GetVertexStrideSize(),
                                                                   part.numvertices,
                                                                   part.p16Indices,
                                                                   part.startindex,
                                                                   part.primitivecount * 3,
                                                                   out adjacencies,
                                                                   out report);
                            adjibuf = new IndexBuffer(device, adjacencies.Length * 2,
                                                              BufferUsage.WriteOnly,
                                                              Microsoft.Xna.Framework.Graphics.IndexElementSize.SixteenBits);
                            adjibuf.SetData<ushort>(adjacencies);
                        }
                        else                        // 32 bit model indices and 32 bit adjacency indices
                        {
                            int[] adjacencies;
                            string report;
                            JBBRXG11ModelAdjacency32.AppendAdjacency(part.pVertices,
                                                                     part.vertexOffset,
                                                                     vertexpositionbyteoffset,
                                                                     vdecl.GetVertexStrideSize(),
                                                                     part.numvertices,
                                                                     part.p32Indices,
                                                                     part.startindex,
                                                                     part.primitivecount * 3,
                                                                     out adjacencies,
                                                                     out report);
                            adjibuf = new IndexBuffer(device, adjacencies.Length * 4,
                                                              BufferUsage.WriteOnly,
                                                              Microsoft.Xna.Framework.Graphics.IndexElementSize.ThirtyTwoBits);
                            adjibuf.SetData<int>(adjacencies);
                        }
                    }

                    BasicEffect effect = new BasicEffect(device);//TODO:EffectPool removed from constructor, null); //AnimatedModelEffectSource, "");
                    if (hasnormals) effect.EnableDefaultLighting();
                    for (int t = 0; t < part.texturefilenames.Length; t++)
                    {
                        string tname;
                        tname = part.texturefilenames[t];  // importer puts relative filename here
                        tname = tname.Substring(0, tname.LastIndexOf('.'));
                        if (t == 0 && hastexture)
                        {
                            effect.Texture = content.Load<Texture2D>(tname);
                            effect.TextureEnabled = true;
                        }
                    }
                    effectlist.Add(effect);

                   //TODO: THIS IS WRONG AppendAdjacency(part.pVertices, part.vertexOffset, vdecl.GetVertexStrideSize(), part.numvertices,
                                  //part.vIndices, part.startindex, part.primitivecount * 3, part.basevertex, adjacencies);
                    //adjibuf.SetData<short>(adjacencies);

                    partlist.Add(new ModelMeshPart(vdecl, vbuf, ibuf, adjibuf, part.vertexOffset, part.numvertices,
                                                   part.primitivecount, part.startindex,
                                                   effect));
                }
                meshlist.Add(new ModelMesh(device, this, mesh.name, mesh.sphere, partlist, effectlist));

                //adjibuf.SetData<short>(adjacencies);
                //TODO: vbuf is undeclared outside of this loop. Meshlist is obviously important. How to rememdy??
                //meshlist.Add(new ModelMesh(device, this, mesh.name, mesh.sphere, vbuf, ibuf, adjibuf, partlist, effectlist));
            }
            meshes = new ModelMeshCollection(meshlist);

            // Initialise animation data
            if (mis.skinning == null || !hastexture || !hasnormals)  // Only animate textured and lit models
            {
                animated = false;
            }
            else
            {
                animated = true;
                animationdata = mis.skinning;

                boneTransforms = new Microsoft.Xna.Framework.Matrix[animationdata.BindPose.Length];
                worldTransforms = new Microsoft.Xna.Framework.Matrix[animationdata.BindPose.Length];
                skinTransforms = new Microsoft.Xna.Framework.Matrix[animationdata.BindPose.Length];

                foreach (ModelMesh mesh in meshes)
                    foreach (BasicEffect eff in mesh.Effects)
                        eff.UseForAnimation = true;

                currentclip = null;  // Not animating at the moment (call StartClip to begin)
                //StartClip("Default Take", Microsoft.Xna.Framework.Matrix.Identity);

                //skinned_effect = Content.Load<Effect>("SkinnedModel");
            }
        }

        /// <summary>
        /// Start a new animation. This is sufficient to get the first pose displayed
        /// Call UpdateAnimation on the next and subsequent frames. Do not call it on
        /// this frame.
        /// </summary>
        /// <param name="clipname"></param>
        public void StartClip(string clipname, Microsoft.Xna.Framework.Matrix rootTransform)
        {
            currentclip = animationdata.AnimationClips[clipname];
            if (currentclip == null) throw new Exception("Animation clip not available");

            currentanimationtime = new TimeSpan(0);
            currentkeyframe = 0;
            animationroot = rootTransform;

            // Initialize bone transforms to the bind pose.
            animationdata.BindPose.CopyTo(boneTransforms, 0);
            TimeSpan zero = new TimeSpan(0);
            UpdateAnimation(new Microsoft.Xna.Framework.GameTime(zero, zero), true);
        }

        public bool UpdateAnimation(Microsoft.Xna.Framework.GameTime time, bool repeat)
        {
            if (!CurrentlyAnimating) return true;

            // Update the animation position.

            // First check time:  may have finished or need a rewind
            TimeSpan newtime = currentanimationtime + time.ElapsedGameTime;
            if (newtime >= currentclip.Duration)
            {
                if (repeat)
                {
                    while (newtime >= currentclip.Duration)
                    {
                        newtime -= currentclip.Duration;
                        //offset.X -= 2.7f;
                    }
                }
                else
                {
                    currentclip = null;
                    return true;
                }
            }

            // If the position moved backwards, reset the keyframe index.
            if (newtime < currentanimationtime)
            {
                currentkeyframe = 0;
                animationdata.BindPose.CopyTo(boneTransforms, 0);
            }

            currentanimationtime = newtime;

            while (currentkeyframe < currentclip.Keyframes.Length)
            {
                Keyframe keyframe = currentclip.Keyframes[currentkeyframe];

                // Stop when we've read up to the current time position.
                if (keyframe.Time > currentanimationtime) break;

                // Use this keyframe.
                boneTransforms[keyframe.Bone] = keyframe.Transform;

                currentkeyframe++;
            }

            // Update world transforms (from bone space to model space)
            // and finally the skin transforms, which are the inverse
            // world transformations from the base pose, multiplied by
            // the world tranforms - ie: conversion from base pose bone
            // space to model space after movement
            for (int bone = 0; bone < worldTransforms.Length; bone++)
            {
                if (bone == 0)
                {
                    worldTransforms[0] = boneTransforms[0] * animationroot;
                }
                else
                {
                    int parentBone = animationdata.SkeletonHierarchy[bone];
                    worldTransforms[bone] = boneTransforms[bone] * worldTransforms[parentBone];
                }
                skinTransforms[bone] = animationdata.InverseBindPose[bone] * worldTransforms[bone];
            }

            return false;
        }

        public void Dispose()
        {
            // All unmanaged objects are on the JBBRXG11 disposable list.
        }
    }


    public class ModelMesh
    {
        GraphicsDevice device;
        Model parent;
        string name;
        Microsoft.Xna.Framework.BoundingSphere sphere;
        VertexBuffer vbuf;
        //IndexBuffer ibuf, adjibuf;
        ModelMeshPartCollection parts;
        ModelEffectCollection all_effects;

        public string Name { get { return name; } }
        public Microsoft.Xna.Framework.BoundingSphere BoundingSphere { get { return sphere; } }
        //public VertexBuffer VertexBuffer { get { return vbuf; } }
        //public IndexBuffer IndexBuffer { get { return ibuf; } }
        public ModelMeshPartCollection MeshParts { get { return parts; } }
        public ModelEffectCollection Effects { get { return all_effects; } }

        internal ModelMesh(GraphicsDevice dev,
                           Model par,
                           string n, Microsoft.Xna.Framework.BoundingSphere s,
                           //VertexBuffer v, IndexBuffer i, IndexBuffer adji,
                           IList<ModelMeshPart> partlist,
                           IList<Effect> effectlist)
        {
            device = dev;
            parent = par;
            name = n;
            sphere = s;
            //vbuf = v;
            //ibuf = i;
            //adjibuf = adji;
            parts = new ModelMeshPartCollection(partlist);
            all_effects = new ModelEffectCollection(effectlist);
        }

        public void Draw()
        {
            foreach (ModelMeshPart part in parts)
            {
                if (part.IndexBuffer != null)
                    device.Indices = part.IndexBuffer;
                if (parent.CurrentlyAnimating)
                {
                    (part.Effect as BasicEffect).UseForAnimation = true;
                    part.Effect.Parameters["Bones"].SetValue(parent.SkinTransforms);
                }
                else
                {
                    if (part.effect is BasicEffect)
                        (part.Effect as BasicEffect).UseForAnimation = false;
                }
                part.Draw(device, PrimitiveType.TriangleList);
            }
        }

        public void DrawWithAdjacency()
        {
            foreach (ModelMeshPart part in parts)
            {
                if (part.AdjIndexBuffer == null)
                {
                    throw new Exception("Adjacency indices have not been computed for the model.  " +
                        "Please set 'Compute adjacency indices' to 'True' in the model's content processor properties");
                }
                device.Indices = part.AdjIndexBuffer;
                if (parent.CurrentlyAnimating)
                {
                    (part.Effect as BasicEffect).UseForAnimation = true;
                    part.Effect.Parameters["Bones"].SetValue(parent.SkinTransforms);
                }
                else
                {
                    if (part.effect is BasicEffect)
                        (part.Effect as BasicEffect).UseForAnimation = false;
                }
                part.Draw(device, PrimitiveType.TriangleListWithAdjacency);
            }
        }
    }

    public sealed class ModelMeshCollection : ReadOnlyCollection<ModelMesh>
    {
        public ModelMeshCollection(IList<ModelMesh> meshes) : base(meshes) { }
    }

    public sealed class ModelMeshPart
    {
        VertexDeclaration vdecl;
        VertexBuffer vbuff;
        IndexBuffer ibuff, adjibuff;
        int vertexOffset, numvertices, primitivecount, startindex;
        internal Effect effect;

        internal ModelMeshPart(VertexDeclaration vd, VertexBuffer vb, IndexBuffer ib, IndexBuffer adjbuf, int vo, int nv, int pc, int si, Effect eff)
        {
            vdecl = vd;
            vbuff = vb;
            ibuff = ib;
            adjibuff = adjbuf;
            vertexOffset = vo;
            numvertices = nv;
            primitivecount = pc;
            startindex = si;
            //streamoffset = so;
            effect = eff;
        }
       
        //     Gets the offset to add to each vertex index in the index buffer.
        public int VertexOffset { get { return vertexOffset; } }
        //     Gets the number of vertices used during a draw call.
        public int NumVertices { get { return numvertices; } }
        //     Gets the number of primitives to render.
        public int PrimitiveCount { get { return primitivecount; } }
        //     Gets the location in the index array at which to start reading vertices.
        public int StartIndex { get { return startindex; } }
        //     Gets the offset in bytes from the beginning of the VertexBuffer.
        //public int StreamOffset { get { return streamoffset; } }

        public Effect Effect { get { return effect; } set { effect = value; } }
        public IndexBuffer IndexBuffer { get { return ibuff; } }
        public IndexBuffer AdjIndexBuffer { get { return adjibuff; } }
        public VertexBuffer VertexBuffer { get { return vbuff; } }
        //public VertexDeclaration VertexDeclaration { get { return vdecl; } }
        public int VertexStride { get { return vdecl.GetVertexStrideSize(); } }

        internal void Draw(GraphicsDevice dev, PrimitiveType primitives)
        {
            dev.VertexDeclaration = vdecl;
            dev.Vertices[0].SetSource(vbuff, 0, vdecl.GetVertexStrideSize());
            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                if (ibuff == null)
                    dev.DrawPrimitives(primitives, vertexOffset, primitivecount);
                else
                    dev.DrawIndexedPrimitives(primitives, vertexOffset, 0, // TODO:  0 should be minimum vertex index
                                              numvertices, (primitives == PrimitiveType.TriangleListWithAdjacency ? 0 : startindex), primitivecount);
                pass.End();
            }
            effect.End();
        }
    }

    public sealed class ModelMeshPartCollection : ReadOnlyCollection<ModelMeshPart>
    {
        public ModelMeshPartCollection(IList<ModelMeshPart> parts) : base(parts) { }
    }

    public sealed class ModelEffectCollection : ReadOnlyCollection<Effect>
    {
        public ModelEffectCollection(IList<Effect> effects) : base(effects) { }
    }
}







/*

        Model

        // Summary:
        //     Gets a collection of ModelBone objects which describe how each mesh in the
        //     Meshes collection for this model relates to its parent mesh. Reference page
        //     contains links to related code samples.
        public ModelBoneCollection Bones { get; }
        //
        // Summary:
        //     Gets the root bone for this model.
        public ModelBone Root { get; }
        //
        // Summary:
        //     Gets or sets an object identifying this model.
        public object Tag { get; set; }

        // Summary:
        //     Copies a transform of each bone in a model relative to all parent bones of
        //     the bone into a given array. Reference page contains links to related code
        //     samples.
        //
        // Parameters:
        //   destinationBoneTransforms:
        //     The array to receive bone transforms.
        public void CopyAbsoluteBoneTransformsTo(Matrix[] destinationBoneTransforms);
        //
        // Summary:
        //     Copies an array of transforms into each bone in the model. Reference page
        //     contains links to related conceptual articles.
        //
        // Parameters:
        //   sourceBoneTransforms:
        //     An array containing new bone transforms.
        public void CopyBoneTransformsFrom(Matrix[] sourceBoneTransforms);
        //
        // Summary:
        //     Copies each bone transform relative only to the parent bone of the model
        //     to a given array. Reference page contains links to related code samples.
        //
        // Parameters:
        //   destinationBoneTransforms:
        //     The array to receive bone transforms.
        public void CopyBoneTransformsTo(Matrix[] destinationBoneTransforms);
    }
 
 
 
     // Summary:
    //     Represents a set of bones associated with a model.
    public sealed class ModelBoneCollection : ReadOnlyCollection<ModelBone>
 

 
 
    ModelMeshPart
        //
        // Summary:
        //     Gets or sets an object identifying this model mesh part.
        public object Tag { get; set; }


 
    ModelMesh
        //
        // Summary:
        //     Gets the parent bone for this mesh. The parent bone of a mesh contains a
        //     transformation matrix that describes how the mesh is located relative to
        //     any parent meshes in a model. Reference page contains links to related code
        //     samples.
        public ModelBone ParentBone { get; }
        //
        // Summary:
        //     Gets or sets an object identifying this mesh.
        public object Tag { get; set; }
        //
        // Summary:
        //     Draws all of the ModelMeshPart objects in this mesh, using their current
        //     Effect settings, and specifying options for saving effect state. Reference
        //     page contains links to related code samples.
        //
        // Parameters:
        //   saveStateMode:
        //     The save state options to pass to each Effect.
        public void Draw(SaveStateMode saveStateMode);
    }
 
 
     // Summary:
    //     Represents bone data for a model. Reference page contains links to related
    //     conceptual articles.
    public sealed class ModelBone
    {
        // Summary:
        //     Gets a collection of bones that are children of this bone.
        public ModelBoneCollection Children { get; }
        //
        // Summary:
        //     Gets the index of this bone in the Bones collection.
        public int Index { get; }
        //
        // Summary:
        //     Gets the name of this bone.
        public string Name { get; }
        //
        // Summary:
        //     Gets the parent of this bone.
        public ModelBone Parent { get; }
        //
        // Summary:
        //     Gets or sets the matrix used to transform this bone relative to its parent
        //     bone. Reference page contains links to related code samples.
        public Matrix Transform { get; set; }
    }
 
 
 */
